﻿using Sidvall.IO.Data;
using Sidvall.Net;
using System.Threading;
using System.Threading.Tasks;

namespace Sidvall.IO
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
    public class NetStream : System.IO.Stream
    {
        #region Public Members

        public NetFileSystem NetFileSystem { get; set; }
        public string Path { get; set; }

        #region CanRead

        public override bool CanRead
        {
            get
            {
                return true;
            }
        }

        #endregion
        #region CanSeek

        public override bool CanSeek
        {
            get
            {
                return true;
            }
        }

        #endregion
        #region CanWrite

        public override bool CanWrite
        {
            get
            {
                return true;
            }
        }

        #endregion
        #region Length

        private long? _Length;
        public override long Length
        {
            get
            {
                return _Length.Value;
            }
        }

        #endregion
        #region Position

        private long _Position = 0;
        public override long Position
        {
            get
            {
                return _Position;
            }
            set
            {
                _Position = value;
            }
        }

        #endregion

        #region Dispose

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.NetFileSystem = null;
            }
            base.Dispose(disposing);
        }

        #endregion
        #region Flush

        public override void Flush()
        {
            var item = new ItemContext()
            {
                Path = this.Path,
            };
            var result = this.NetFileSystem.GetResultAsync(Sidvall.IO.Security.ResourceType.File, Sidvall.IO.FileSystemOperation.Flush, null,
                null, HttpMethod.Post, item, null, CancellationToken.None).Result;
            result.EnsureSuccessStatusCode();
        }

        #endregion
        #region FlushAsync

        public override async Task FlushAsync(CancellationToken cancellationToken)
        {
            var item = new ItemContext()
            {
                Path = this.Path,
            };
            var result = await this.NetFileSystem.GetResultAsync(Sidvall.IO.Security.ResourceType.File, Sidvall.IO.FileSystemOperation.Flush, null,
                null, HttpMethod.Post, item, null, cancellationToken).ConfigureAwait(false);
            await result.EnsureSuccessStatusCodeAsync().ConfigureAwait(false);
        }

        #endregion
        #region Read

        public override int Read(byte[] buffer, int offset, int count)
        {
            int length;

            if (this.Position < _Length)
            {
                length = DownloadRangeToByteArrayAsync(buffer, offset, this.Position, count, CancellationToken.None).Result;
                this.Position += length;
                return length;
            }
            return 0;
        }

        #endregion
        #region ReadAsync

        public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            int length;

            if (this.Position < _Length)
            {
                length = await DownloadRangeToByteArrayAsync(buffer, offset, this.Position, count, cancellationToken).ConfigureAwait(false);
                this.Position += length;
                return length;
            }
            return 0;
        }

        #endregion
        #region Seek

        public override long Seek(long offset, System.IO.SeekOrigin origin)
        {
            switch (origin)
            {
                case System.IO.SeekOrigin.Begin:
                    this.Position = offset;
                    break;
                case System.IO.SeekOrigin.Current:
                    this.Position += offset;
                    break;
                case System.IO.SeekOrigin.End:
                    this.Position = this.Length - offset;
                    break;
                default:
                    break;
            }
            return this.Position;
        }

        #endregion
        #region SetLength

        public override void SetLength(long value)
        {
            _Length = value;
        }

        #endregion
        #region Write

        public override void Write(byte[] buffer, int offset, int count)
        {
            if (count > 0)
            {
                UploadFromByteArrayAsync(buffer, offset, this.Position, count, CancellationToken.None).Wait();
                this.Position += count;
                if ((_Length == null) || (_Length < this.Position))
                    SetLength(this.Position);
            }
        }

        #endregion
        #region WriteAsync

        public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            if (count > 0)
            {
                await UploadFromByteArrayAsync(buffer, offset, this.Position, count, cancellationToken).ConfigureAwait(false);
                this.Position += count;
                if ((_Length == null) || (_Length < this.Position))
                    SetLength(this.Position);
            }
        }

        #endregion

        #endregion
        #region Private Members

        #region DownloadRangeToByteArrayAsync

        private async Task<int> DownloadRangeToByteArrayAsync(byte[] buffer, int offset, long position, int count, CancellationToken cancellationToken)
        {
            HttpResponse result;
            long length;

            cancellationToken.ThrowIfCancellationRequested();
            if (this.NetFileSystem.UsePost)
            {
                var item2 = new ReadFileContext()
                {
                    Path = this.Path,
                    Position = position,
                    Length = count,
                };
                result = await this.NetFileSystem.GetResultAsync(Sidvall.IO.Security.ResourceType.File, Sidvall.IO.FileSystemOperation.Read, null,
                    null, HttpMethod.Post, item2, null, cancellationToken).ConfigureAwait(false);
            }
            else
            {
                var range = new RangeHeaderValue()
                {
                    From = position,
                    To = position + count - 1,
                };
                if (range.To > _Length - 1)
                {
                    range.To = _Length - 1;
                    if (range.From > range.To)
                        return 0;
                }
                result = await this.NetFileSystem.GetResultAsync(Sidvall.IO.Security.ResourceType.File, Sidvall.IO.FileSystemOperation.Read, this.Path,
                   null, HttpMethod.Post, null, range, cancellationToken).ConfigureAwait(false);
            }
            await result.EnsureSuccessStatusCodeAsync().ConfigureAwait(false);
            using (var s = new System.IO.MemoryStream(buffer, offset, count))
            {
                await result.CopyContentToAsync(s).ConfigureAwait(false);
                length = s.Position;
            }
            position += length;
            return (int)length;
        }

        #endregion
        #region UploadFromByteArrayAsync

        private async Task UploadFromByteArrayAsync(byte[] buffer, int offset, long position, int count, CancellationToken cancellationToken)
        {
            byte[] bytes;

            cancellationToken.ThrowIfCancellationRequested();
            var item = new WriteFileContext()
            {
                Path = this.Path,
                Position = position,
            };
            if ((offset == 0) && (buffer.Length == count))
            {
                item.Buffer = buffer;
            }
            else
            {
                bytes = new byte[count];
                System.Buffer.BlockCopy(buffer, offset, bytes, 0, count);
                item.Buffer = bytes;
            }
            var result = await this.NetFileSystem.GetResultAsync(Sidvall.IO.Security.ResourceType.File, Sidvall.IO.FileSystemOperation.WriteBuffer, null,
                null, HttpMethod.Post, item, null, cancellationToken).ConfigureAwait(false);
            await result.EnsureSuccessStatusCodeAsync().ConfigureAwait(false);
        }

        #endregion

        #endregion
    }
}
